-- VS

uniform Matrices {
    mat4 Projection;
    mat4 Camera;
    mat3 NormalC;
} GlobalMatrices;

uniform Model {
    mat4 ModelView;
    mat3 NormalM;
} LocalModel;

struct VSAttrs {
    vec4 Position;
};

in VSAttrs VSIn;

void main()
{
    gl_Position=GlobalMatrices.Camera*(LocalModel.ModelView*VSIn.Position);
}


-- TC

// displacement patch
layout(vertices = 1) out;

uniform Matrices {
    mat4 Projection;
    mat4 Camera;
    mat3 NormalC;
} GlobalMatrices;

patch out vec3 cSu;
patch out vec3 cSv;
patch out vec3 cSn;
patch out vec4 cLods;

#define ID gl_InvocationID

void main()
{
    mat4 coordsMat=GlobalMatrices.Projection*mat4(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position, gl_in[3].gl_Position);
    vec4 projectedz=transpose(coordsMat)[2];
#ifdef ZBUFFER
    vec4 projectedw=transpose(coordsMat)[3];
    projectedz/=projectedw;
    projectedz=projectedz*0.5+0.5;  // [-1,1] --> [0,1]

    vec4 lods=clamp(-log2(projectedz)*2000, 1, gl_MaxTessGenLevel);
    cLods=(1-lods/gl_MaxTessGenLevel);
    cLods*=8*cLods;
#else
    // We have a wbuffer, projectedz is already in [-1,1]
    vec4 t=smoothstep(-1,1,projectedz);
    vec4 lods=gl_MaxTessGenLevel*(1-t*t);
    cLods=8*t;
#endif

    lods=max(lods,lods.yyzw);
    lods=max(lods,lods.zzzw);
    lods=max(lods,lods.wwww);
    float lod=lods.x;
    gl_TessLevelInner[0]=lod;
    gl_TessLevelInner[1]=lod;
    gl_TessLevelOuter[0]=lod;
    gl_TessLevelOuter[1]=lod;
    gl_TessLevelOuter[2]=lod;
    gl_TessLevelOuter[3]=lod;

    // ID is 0
    // 3--2
    // |  |
    // |  |
    // 0--1
    gl_out[ID].gl_Position=gl_in[0].gl_Position;
    cSu = (gl_in[1].gl_Position-gl_in[0].gl_Position).xyz;
    cSv = (gl_in[3].gl_Position-gl_in[0].gl_Position).xyz;
    cSn = normalize(cross(cSu, cSv));
}


-- TE

uniform Matrices {
    mat4 Projection;
    mat4 Camera;
    mat3 NormalC;
} GlobalMatrices;

uniform MaterialAttrs {
    vec4 diffuse;
    vec4 extra;
} Material;

layout(quads, equal_spacing, ccw) in;

uniform sampler2D TexDisp;

patch in vec3 cSu;
patch in vec3 cSv;
patch in vec3 cSn;
patch in vec4 cLods;

out FSAttrs {
    vec3 Normal;
    vec3 Location;
} TEOut;

void main()
{
    vec4 origin=gl_in[0].gl_Position;

    // compute lod
    // 3--2
    // |  |
    // |  |
    // 0--1
    vec4 xcrds=vec4(0,1,1,0)-gl_TessCoord.xxxx;
    vec4 ycrds=vec4(0,0,1,1)-gl_TessCoord.yyyy;
    xcrds*=xcrds;
    ycrds*=ycrds;
    vec4 dsts=sqrt(xcrds+ycrds);
    dsts/=dot(dsts,vec4(1));
    float lod=dot(dsts,cLods);

    // sample position and etc...
    float scale=Material.extra.x;
    vec3 pos=gl_TessCoord.x*cSu + gl_TessCoord.y*cSv + textureLod(TexDisp, gl_TessCoord.xy, lod).r*scale*cSn;

    vec4 loc=origin+vec4(pos, 0);
    TEOut.Location=loc.xyz;
    gl_Position=GlobalMatrices.Projection*loc;
#ifndef ZBUFFER
    // wbuffer
    gl_Position.z*=gl_Position.w;
#endif
    
    vec2 SvSu=vec2(length(cSv), length(cSu));
    vec2 ratios=SvSu.xy/SvSu.yx;
    vec2 dh=vec2(   textureLodOffset(TexDisp, gl_TessCoord.xy, lod, ivec2(1,0)).r-textureLodOffset(TexDisp, gl_TessCoord.xy, lod, ivec2(-1,0)).r,
                    textureLodOffset(TexDisp, gl_TessCoord.xy, lod, ivec2(0,1)).r-textureLodOffset(TexDisp, gl_TessCoord.xy, lod, ivec2(0,-1)).r);
    vec2 dispSize=vec2(textureSize(TexDisp, int(lod)));
    dispSize*=0.5*dh*ratios;
    TEOut.Normal=SvSu.x*SvSu.y*cSn-scale*(dispSize.x*cSu+dispSize.y*cSv);
}

-- FS


#define INVERSE_4PI 0.07957747154594766788444188168626
const vec3 cEye=vec3(0,0,0);

struct Light {
    vec3 pos;
    vec4 color;    
};

uniform Lights {
    Light lights[MAX_LIGHTS];
} GlobalLights;

vec3 Lighting(in vec3 normal, in vec3 location, in vec4 mat)
{
    vec3 outColor=vec3(0,0,0);
    vec3 view=normalize(cEye-location);
    for(int i=0; i<MAX_LIGHTS; i++)
        if(GlobalLights.lights[i].color.a > 0) {
            vec3 incoming=location-GlobalLights.lights[i].pos;
            float aten=inversesqrt(dot(incoming,incoming));
            incoming*=aten;
            aten*=aten*INVERSE_4PI*GlobalLights.lights[i].color.a;
            outColor-=min(0, dot(incoming, normal))*GlobalLights.lights[i].color.rgb*aten*mat.rgb;
            // Obtain <N,H>
            vec3 reflected=reflect(incoming, normal);
            float spec=(dot(reflected, view)+1)*0.5;
            float smoothness=1/(mat.a*mat.a);
            spec=1/spec;
            // compute the coefficient
            spec=exp((1-spec)*smoothness)*smoothness*spec*spec*INVERSE_4PI*4;
            // nullify in case of backface
            spec*=(sign(dot(reflected,normal))+1)*0.5;
            outColor+=spec*GlobalLights.lights[i].color.rgb*aten;
        }
    return outColor;
}

uniform MaterialAttrs {
    vec4 diffuse;
    vec4 extra;
} Material;

in FSAttrs {
    vec3 Normal;
    vec3 Location;
} FSIn;

out vec4 fColor;

void main()
{
    vec3 normal=normalize(FSIn.Normal);
    vec4 outColor=vec4(0,0,0,1);
    outColor.rgb=Lighting(normal, FSIn.Location, Material.diffuse);

    fColor=outColor;
    //gl_FragDepth=gl_FragCoord.z*gl_FragCoord.z;
}
